<?php
/**
 * Created by PhpStorm.
 * User: wiggin
 * Date: 2017/11/18
 */

namespace App\Utility\Curl;

class MultiCurl
{
    protected $mh = null;
    protected $chs = array();
    protected $url = '';
    protected $limit = 50;      //设置一次批量请求的数量
    protected $sleepTimes = 500;

    protected $curlOptions = array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_AUTOREFERER => true,
        CURLOPT_CONNECTTIMEOUT => 30,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_SSL_VERIFYPEER => false,
        //CURLOPT_PROXY => '127.0.0.1:8888', //增加代理调试功能(便于抓包)
        CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20110619 Firefox/5.0'
    );

    /**
     * MultiCurl constructor.
     * @param $url
     * @param array $options
     */
    public function __construct($url, $options = array())
    {
        $this->mh = curl_multi_init();
        $this->curlOptions[CURLOPT_URL] = $url;
        if (!empty($options)) {
            foreach ($options as $k => $v) {
                $this->curlOptions[$k] = $v;
            }
        }
    }

    /**
     * 设置本次批量请求的公用头部 如果单独设置，需要在设置单个请求中去设置头部信息
     * @param array $header
     * @return $this
     */
    public function setHeaders($header)
    {
        if (empty($header)) {
            return $this;
        }
        if ($this->_isAssoc($header)) {
            $out = array();
            foreach ($header as $k => $v) {
                $out[] = $k . ': ' . $v;
            }
            $header = $out;
        }
        $this->curlOptions[CURLOPT_HTTPHEADER] = $header;
        return $this;
    }

    /**
     * 设置get的curl句柄
     * @param $key
     * @param string $url
     * @param array $options
     */
    public function setGetCurl($key, $url = '', $options = array())
    {
        $this->chs[$key] = curl_init();
        $optionArr = $this->curlOptions;
        if (!empty($options)) {
            foreach ($options as $k => $v) {
                $optionArr[$k] = $v;
            }
        }
        if (!empty($url)) {
            $optionArr[CURLOPT_URL] = $url;
        }
        $optionArr[CURLOPT_HTTPGET] = true;
        curl_setopt_array($this->chs[$key], $optionArr);
        curl_multi_add_handle($this->mh, $this->chs[$key]);
    }

    /**
     * 设置post的curl句柄
     * @param $key
     * @param $postData
     * @param string $url
     * @param array $options
     */
    public function setPostCurl($key, $postData, $url = '', $options = array())
    {
        $this->chs[$key] = curl_init();
        $optionArr = $this->curlOptions;
        if (!empty($options)) {
            foreach ($options as $k => $v) {
                $optionArr[$k] = $v;
            }
        }
        if (!empty($url)) {
            $optionArr[CURLOPT_URL] = $url;
        }
        $optionArr[CURLOPT_POST] = true;
        $optionArr[CURLOPT_POSTFIELDS] = $postData;
        curl_setopt_array($this->chs[$key], $optionArr);
        curl_multi_add_handle($this->mh, $this->chs[$key]);
    }

    /**
     * 发送批量的curl句柄请求
     * @return $this|bool
     */
    public function sendRequest()
    {
        if (empty($this->mh)) {
            throw new Exception('curl_multi_init Fail');
        }
        if (count($this->chs) > $this->limit) {
            throw new Exception('curl_multi Exceed This limit Numbers ' . $this->limit);
        }
        //执行批处理句柄
        $active = 0;
        do {
            $mrc = curl_multi_exec($this->mh, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);

        while ($active && $mrc == CURLM_OK) {
            $status = curl_multi_select($this->mh);
            if ($status == -1) {
                usleep($this->sleepTimes);
            }
            do {
                $mrc = curl_multi_exec($this->mh, $active);
            } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        }
        return $this;
    }

    /**
     * 获取批量处理的请求结果
     * @return array|bool
     */
    public function getResponse()
    {
        if (empty($this->mh) || empty($this->chs)) {
            throw new Exception('curl_multi_init Fail Or none curl_handles');
        }
        $response = array();
        foreach ($this->chs as $key => $val) {
            //$info = curl_getinfo($val);
            $error = curl_error($val);
            $result = curl_multi_getcontent($val);
            if (!empty($error)) {
                $response[$key] = array(
                    'status' => false,
                    'errorMsg' => $error
                );
            } elseif (!empty($result)) {
                $response[$key] = array(
                    'status' => true,
                    'data' => $result
                );
            } else {
                $response[$key] = array(
                    'status' => false,
                    'errorMsg' => 'curl请求未知错误'
                );
            }
//            $response[$key] = compact('error', 'result');
            curl_multi_remove_handle($this->mh, $val);
            curl_close($val);
        }
        curl_multi_close($this->mh);
        return $response;
    }

    /**
     * 释放资源
     */
    public function __destruct()
    {
        if (!empty($this->mh) && is_resource($this->mh)) {
            curl_multi_close($this->mh);
        }
        if (!empty($this->chs)) {
            foreach ($this->chs as $val) {
                if (is_resource($val)) {
                    curl_close($val);
                }
            }
        }

    }

    /**
     * @param $arr
     * @return bool
     */
    private function _isAssoc($arr)
    {
        return array_keys($arr) !== range(0, count($arr) - 1);
    }
}